/*
 * PBBCitation.java
 *
 * Created on August 14, 2007, 10:30 PM
 *
 *Copyright 2007-2008 Jon W Huss III

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 *
 */

package proteinboxbot;

import java.io.*;
import java.net.*;
import java.util.*;
import proteinboxbot.*;

/**
 *
 * @author Jon Huss
 */
public class PBBCitation {
    
    private PBBLogging glblLog;
    private PBBxml glblXml;
    
    private Date nextComm;
    
    /**
     * Creates a new instance of PBBCitation.
     *
     * @param pbbLog The instance of PBBLogging to use
     * @param xml The Instance of PBBxml to use
     * 
     */
    public PBBCitation(PBBLogging pbbLog, PBBxml xml) {
        glblLog = pbbLog;
        glblXml = xml;
        nextComm = new Date();
    }
    
    /**
     * Returns Citation information for a given gene.  The citations are taken from the Entrez Pubmed Database
     * This method error wraps getCitationsH, which is the real workhorse.
     *
     * @param geneNumber The Entrez Gene ID Number
     * @return A String array of formatted Citations
     */
    
    public String[] getCitations(String geneNumber){
        int tries = 0;
        //wraps for error catching.
        boolean clear = false;
        try {
            while (!clear){
                
                try {
                    String[] data = getCitationsH(geneNumber);
                    return data;
                } catch(Exception e) {
                    System.out.println("Entrez: Encountered Error while gathering Citations:");
                    System.out.println(e.getMessage());
                    e.printStackTrace(System.err);
                    if (tries < 3){
                        System.out.println("Trying "+ (3 - tries) + " more time(s) in 10 seconds...");
                        this.updateWait(10000);
                        this.allowComm();
                        tries++;
                        
                    } else {
                        System.out.println("3 tries failed; Requiring User Input:");
                        tries = 0;
                        
                        try {
                            BufferedReader inputLine = new BufferedReader(new InputStreamReader(System.in));
                            System.out.println("Try Again? (y/n)");
                            System.out.flush();
                            String input = inputLine.readLine();
                            //input = "y";
                            if (input.compareToIgnoreCase("y") == 0){
                                clear= false;
                            }else {
                                clear = true;
                                throw new PBBWikiException("User Aborted Citations due to error.",e);
                                
                            }
                            
                        } catch (IOException ex) {
                            throw new PBBWikiException("Unrecoverable IO Error.",ex);
                        }
                    }
                }
            }
        }catch (Exception e){
            //we got a show stopping exception - at this point, just return null.
            System.out.println("Really bad Exception encountered.  Returning Null for Citations.");
            return null;
        }
        
        return null;
    }
    
    //Should be able to just pass in a gene number and get the citations back.
    /**
     * Downloads citations information from the Entrez database.  Performs up to 3 database queries
     * before returning the citation data for a given geneID.  There may be a bug or glitch somewhere
     * in this code that causes a downoad of the 3rd query data to hang.  It happens very infrequently.
     * Future work could involve using a secondary thread to download the data, if the data is not recovered
     * in a resonable amount of time, then the thread is terminated and a new thread is tried (or
     * an exception is thrown).
     *
     * @param geneNumber Entrez Gene ID Number
     * @return String array of formatted Citations
     * @throws Exception if anything goes wrong.
     */
    private String[] getCitationsH(String geneNumber) throws Exception{
        String[] citations = null;
        
        String xml1 = "";
        String xml2 = "";
        String lineSep = System.getProperty("line.separator");
        
        //ok so first query the link tool...
        geneNumber = geneNumber.trim();
        String page1 = "http://eutils.ncbi.nlm.nih.gov/entrez/eutils/elink.fcgi?dbfrom=gene&id="+geneNumber+"&db=pubmed&linkname=gene_pubmed&cmd=neighbor_history";
        
        //try{
        this.allowComm();
        xml1 = downloadEntrezPage(page1);
        this.updateWait(1000);
        
        String[] queryKeyAndWebEnv;
        queryKeyAndWebEnv = glblXml.xmlGetQueryKeyAndWebEnv(xml1);
        
        if (queryKeyAndWebEnv == null){
            throw new Exception("Problem parsing Query Key.");
        }
        if (queryKeyAndWebEnv.length != 2){
            throw new Exception("Problem parsing Query Key.");
        }
        
        //now that we got the querykey and web env...
        //We can query the fetch tool. Note that this is filtered for Review Articles Only.
        String page2 = "http://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&sort=pub+date&filter=review&retmode=xml&query_key="+queryKeyAndWebEnv[0] + "&WebEnv=" + queryKeyAndWebEnv[1];
        System.out.println("Downloading Entrez Article info...");
        this.allowComm();
        xml2 = downloadEntrezPage(page2);
        this.updateWait(2000);
        
        System.out.println("Finished Downloading.");
        //the money right here..
        this.allowComm();
        //We want to include all Review articles, hence the limit of 1000
        citations = glblXml.xmlCiteBuilder(xml2, 1000);
        this.updateWait(2000);
        
        //We only want to include non-review articles if we have less than 20 total.
        int citeMin = 20;
        if (citations.length < citeMin) {
            //we need a few more citations if we can get em..
            String page3 = "http://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&retmax=" + citeMin + "&sort=pub+date&retmode=xml&query_key="+queryKeyAndWebEnv[0] + "&WebEnv=" + queryKeyAndWebEnv[1];
            System.out.println("Downloading Entrez Article info...");
            this.allowComm();
            String xml3 = downloadEntrezPage(page3);
            this.updateWait(2000);
            
            //get some additional citations (maybe)
            String[] citations2 = glblXml.xmlCiteBuilder(xml3, citeMin - citations.length);
            
            //need to combine the arrays without repeats, this is done with a vector.
            //note that we could return less than the citeMin amount, but we will always return at least
            //1/2 of the citeMin amount.
            
            Vector<String> cit3 = new Vector<String>();
            for ( int i = 0; i < citations.length; i++){
                cit3.add(citations[i]);
            }
            for (int i = 0; i < citations2.length; i++) {
                if (cit3.contains(citations2[i]) == false) {
                    cit3.add(citations2[i]);
                }
            }
            if (cit3.size() > 0){
                String[] tmpStrArray = new String[1];
                
                citations = cit3.toArray(tmpStrArray);
            }
        }
           
        return citations;
    }
    
    /**
     * Error wrapper for downloading Entrez xml pages.
     *
     * @param page URL of the page
     * @return String representation of the page data.
     */
    private String downloadEntrezPage(String page) throws PBBWikiException{
        //wraps for error catching.
        boolean clear = false;
        int tries = 0;
        while (!clear){
            
            try {
                String data = downloadEntrezPageH(page);
                return data;
            } catch(Exception e) {
                System.out.println("Entrez: Encountered Error while Downloading:");
                System.out.println(e.getMessage());
                e.printStackTrace(System.err);
                if (tries < 3){
                    System.out.println("Trying "+ (3 - tries) + " more time(s) in 10 seconds...");
                    this.updateWait(10000);
                    this.allowComm();
                    tries++;
                    
                    
                } else {
                    System.out.println("3 tries failed; Requiring User Input:");
                    tries = 0;
                    
                    try {
                        BufferedReader inputLine = new BufferedReader(new InputStreamReader(System.in));
                        System.out.println("Try Again? (y/n)");
                        System.out.flush();
                        String input = inputLine.readLine();
                        //input = "y";
                        if (input.compareToIgnoreCase("y") == 0){
                            clear= false;
                        }else {
                            clear = true;
                            throw new PBBWikiException("User Aborted Download due to error.",e);
                            
                        }
                        
                    } catch (IOException ex) {
                        throw new PBBWikiException("Unrecoverable IO Error.",ex);
                        //System.out.println("Unrecoverable IO Error.");
                        //ex.printStackTrace();
                        //System.exit(1);
                    }
                }
            }
        }
        throw new PBBWikiException("Unknown Download Error.");
        //return null;
    }
    
    
    /**
     * Actually downloads data from the given URL.
     * @param page The URL of the data to be downloaded.
     * @return The page of data as a string.
     */
    private String downloadEntrezPageH(String page) throws Exception{
        String lineSep = System.getProperty("line.separator");
        //page = encode(page);
        URL entURL;
        URLConnection entConn;
        
        entURL = new URL(page);
        glblLog.addEntry("SYSTEM","ENTREZCOMM","Downloading File:"+ entURL.toString(),PBBLogging.logs.SY);
        
        entConn = entURL.openConnection();
        
        entConn.getContent();
        int tmpint = entConn.getContentLength();
        if (tmpint > 0) {
            System.out.println("Downloading approximately " + tmpint + " characters of data.");
            System.out.flush();
        }
        //String input = "";
        //BufferedReader entBR = new BufferedReader(new InputStreamReader(entConn.getInputStream(),"UTF-8"));
        //String inLine = "";
        //while ( (inLine = entBR.readLine()) != null) {
        //    input += inLine + lineSep;
        //}
        //
        //entBR.close();
        
        //Try a different approach to see if we can stop an error down the line..
        //This method is much faster for communication it seems.
        InputStream is = entConn.getInputStream();
        int numRead;
        byte[] input = new byte[1024];
        byte[] byteData = new byte[0];
        byte[] tmp = null;
        while ( (numRead = is.read(input)) != -1) {
            tmp = byteData;
            byteData = new byte[tmp.length+numRead];
            System.arraycopy(tmp,0,byteData,0,tmp.length);
            System.arraycopy(input,0,byteData,tmp.length,numRead);
        }
        is.close();
        String fullData = new String(byteData,"UTF-8");
        
        
        //BufferedWriter bw = new BufferedWriter(new FileWriter(destination));
        return fullData;
        
    }
    
    /**
     * A wrapper for the URLEncoder.encode method.  Error catch is built into this function.
     *
     * @param in The String to be encoded.
     * @return Formatted String based on URLEncoder.encode method
     */
    public static String encode(String in) {
        //Quick shortcut
        try {
            return URLEncoder.encode(in, "UTF-8");
        }catch (Exception e){
            System.out.println("Error While Encoding.");
            return in;
        }
    }
    
    
    /**
     * Increases the time until the Program is allowed to download again.
     * @param wait Time in Milliseconds
     */
    private void updateWait(int wait){
        Date now = new Date();
        nextComm = new Date(now.getTime() + wait);
    }
    
    
    /**
     * Pauses the execution of the program until a certain time.
     */
    private void allowComm() {
        //this function makes the bot wait until it is allowed to perform the next communication with Wiki.
        Date now = new Date();
        
        long wait = nextComm.getTime() - now.getTime();
        //System.out.println("Wait = " + wait);
        double waitTime = ((double)wait)/1000;
        if (wait > 0) {
            System.out.println("Citation: Waiting " + waitTime + " seconds for cooldown.");
            System.out.flush();
            long left = wait;
            while( left > 10){
                System.out.print(".");
                System.out.print(" ");
                System.out.flush();
                try {
                    if( left > 1000){
                        Thread.currentThread().sleep(1000);
                        left = left - 1000;
                    }else {
                        Thread.currentThread().sleep(left);
                        left = 0;
                        
                    }
                } catch (InterruptedException ex) {
                    System.out.println("Interrupted While Sleeping. Non-hazardous Error (ignored).");
                    //ex.printStackTrace();
                }
                
                
            }
            Date time2 = new Date();
            long actWait = time2.getTime() - now.getTime();
            System.out.println(" ("+ actWait + " milliseconds.)");
            glblLog.addEntry("SYSTEM","SLEPT","Citation Waited " + actWait + " milliseconds for Cooldown.",PBBLogging.logs.SY);
            
        }
        return;
        
    }
}
